package com.googlecode.yoohoo.xmppcore.protocol.stanza.error;

import com.googlecode.yoohoo.xmppcore.protocol.AbstractError;
import com.googlecode.yoohoo.xmppcore.protocol.IError;
import com.googlecode.yoohoo.xmppcore.protocol.XmlLangText;
import com.googlecode.yoohoo.xmppcore.protocol.parsing.ProtocolKey;
import com.googlecode.yoohoo.xmppcore.protocol.stanza.Stanza;
import com.googlecode.yoohoo.xmppcore.protocol.stanza.StanzaKind;
import com.googlecode.yoohoo.xmppcore.protocol.translation.IXmlWriter;

public class StanzaError extends Stanza implements IError {
	private static final String ERROR_STRING = "error";

	public static final String NAMESPACE_STANZA_ERROR = "urn:ietf:params:xml:ns:xmpp-stanzas";
	
	public static final ProtocolKey PROTOCOL_KEY = new ProtocolKey("error", null);
	public static final ProtocolKey PROTOCOL_KEY_ERROR_DETAIL = new ProtocolKey(NAMESPACE_STANZA_ERROR);
	
	protected StanzaKind stanzaKind;
	protected boolean attachSenderXml;
	protected String senderXml;
	protected ErrorType errorType;
	
	protected AbstractError error;
	
	public StanzaError() {
		this(null);
	}
	
	public StanzaError(ErrorType errorType) {
		this(errorType, null);
	}
	
	public StanzaError(ErrorType errorType, String definedCondition) {
		this(errorType, definedCondition, null);
	}
	
	public StanzaError(ErrorType errorType, String definedCondition, XmlLangText text) {
		error = new StanzaErrorImpl();
		error.setDefinedCondition(definedCondition);
		error.setText(text);
		this.errorType = errorType;
		type = ERROR_STRING;
	}
	
	public void setSenderXml(String senderXml) {
		this.senderXml = senderXml;
	}
	
	public String getSenderXml() {
		return senderXml;
	}
	
	// stanza errors are recoverable(rfc3920 9.3)
	@Override
	public boolean shouldCloseStream() {
		return false;
	}

	@Override
	protected String[] getValidTypes() {
		return new String[] {ERROR_STRING};
	}

	@Override
	public Object getApplicationSpecificCondition() {
		return error.getApplicationSpecificCondition();
	}

	@Override
	public String getDefinedCondition() {
		return error.getDefinedCondition();
	}

	@Override
	public XmlLangText getText() {
		return error.getText();
	}

	@Override
	public void setApplicationSpecificCondition(Object applicationSpecificCondition) {
		error.setApplicationSpecificCondition(applicationSpecificCondition);
	}

	@Override
	public void setDefinedCondition(String definedCondition) {
		error.setDefinedCondition(definedCondition);
	}

	@Override
	public void setText(XmlLangText text) {
		error.setText(text);
	}

	public StanzaKind getStanzaKind() {
		return stanzaKind;
	}

	public ErrorType getErrorType() {
		return errorType;
	}

	public void setStanzaKind(StanzaKind stanzaKind) {
		this.stanzaKind = stanzaKind;
	}

	public void setErrorType(ErrorType errorType) {
		this.errorType = errorType;
	}
	
	public void setAttachSenderXml(boolean attachSenderXml) {
		this.attachSenderXml = attachSenderXml;
	}
	
	public boolean getAttachSenderXml() {
		return attachSenderXml;
	}
	
	@Override
	public void setType(String type) {
		// can't change type
	}
	
	@Override
	public String toXml() {
		amendStanzaInfo();
		return super.toXml();
	}

	@Override
	protected void doToXml(IXmlWriter writer) {
		writer.writeCharacters(error.toXml(), false);
	}
	
	public void amendStanzaInfo() {
		if (senderXml == null)
			return;
		
		String stanzaStartTagPart = getStanzaStartTagPart();
		
		if (stanzaKind == null) {
			int stanzaKindEnd = stanzaStartTagPart.indexOf(' ');
			if (stanzaKindEnd == -1) {
				throw new IllegalArgumentException("Invalid stanza xml. Can't amend stanza error.");
			}
			
			String stanzaKindString = stanzaStartTagPart.substring(1, stanzaKindEnd);
			for (StanzaKind stanzaKind : StanzaKind.values()) {
				if (stanzaKind.toString().equals(stanzaKindString)) {
					this.stanzaKind = stanzaKind;
					break;
				}
			}
			
			if (stanzaKind == null) {
				throw new IllegalArgumentException("Invalid stanza xml. Can't amend stanza error.");
			}
		}

		if (id == null) {
			int idStart = stanzaStartTagPart.indexOf("id='");
			if (idStart == -1) {
				return;
			}
			
			int idEnd = stanzaStartTagPart.indexOf('\'', idStart + 4);
			if (idEnd == -1) {
				throw new IllegalArgumentException("Invalid stanza xml. Can't amend stanza error.");
			}
			
			id = stanzaStartTagPart.substring(idStart + 4, idEnd);
		}
	}
	
	private String getStanzaStartTagPart() {
		if (!senderXml.startsWith("<")) {
			throw new IllegalArgumentException("Invalid stanza xml.");
		}
		
		int tagEnd = senderXml.indexOf('>');
		if (tagEnd == -1) {
			throw new IllegalArgumentException("Invalid stanza xml.");
		}
		
		return senderXml.substring(0, tagEnd + 1);
	}
	
	private class StanzaErrorImpl extends AbstractError {
		@Override
		public boolean shouldCloseStream() {
			return false;
		}

		@Override
		protected String getDefinedConditionNamespace() {
			return NAMESPACE_STANZA_ERROR;
		}

		@Override
		protected void writeErrorHeader(IXmlWriter writer) {
			writer.writeStartElement(ERROR_STRING);
			if (errorType == null) {
				throw new IllegalStateException("Error type hasn't be set.");
			}
			writer.writeAttribute("type", errorType.toString());
		}

		@Override
		protected void writeErrorFooter(IXmlWriter writer) {
			writer.writeEndElement();
		}
	}
}
